using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace Decav.Data
{
    /// <summary>
    /// A reference to an object in a data store that is not loaded until the first request of the object.
    /// </summary>
    /// <typeparam name="TKey">The type of the ID that uniquely identifies the object to load.</typeparam>
    /// <typeparam name="TValue">The type of the value that will be loaded.</typeparam>
    [Serializable]
    public abstract class LazyLoadReference<TKey, TValue>
        where TKey : struct
    {
        /// <summary>
        /// Initializes a new <see cref="LazyLoadReference"/> by the ID of the item to lazy load.
        /// </summary>
        /// <param name="id">The ID of the item to lazy load.</param>
        public LazyLoadReference(TKey id)
        {
            _key = id;
        }

        /// <summary>
        /// Creates a new pre-populated lazy-load reference.  The reference will nto go to the database to
        /// retrieve its value, but will consider itself loaded.
        /// </summary>
        /// <param name="key">The value's key.</param>
        /// <param name="value">The value of the <see cref="LazyLoadReference"/>.</param>
        public LazyLoadReference(TKey key, TValue value)
        {
            _key = key;
            _value = value;
            _loaded = true;
        }

        private bool _loaded;
        private object _syncRoot = new object();
        private TValue _value;
        private TKey _key;

        /// <summary>
        /// Gets whether or not the value has already been loaded from the datastore.
        /// </summary>
        public bool Loaded
        {
            get
            {
                lock(_syncRoot)
                    return _loaded;
            }
        }

        /// <summary>
        /// Gets the unique identifier of the record to load.
        /// </summary>
        public TKey Id
        {
            get
            {
                return _key;
            }
        }

        /// <summary>
        /// Gets the value that was loaded from the datastore.
        /// </summary>
        /// <remarks>
        /// NOTE TO CALLERS:  The value here will be loaded on the first call from the
        /// data store, causing a possible delay in the load of the value.  Future requests
        /// will be cached.
        /// </remarks>
        public TValue Value
        {
            get
            {
                EnsureLoaded();
                return _value;
            }
        }

        /// <summary>
        /// Loads the <see cref="Value"/> if it has not already been loaded.
        /// </summary>
        public void EnsureLoaded()
        {
            lock (_syncRoot)
            {
                if (!_loaded)
                {
                    _value = LoadValue();
                    _loaded = true;
                }
            }
        }

        #region Asynchronous Load
        /// <summary>
        /// Begins an asynchrounous load of the <see cref="Value"/> of the lazy load.
        /// </summary>
        /// <param name="callback">The callback on which the object should call back on when
        /// the load has been completed.</param>
        /// <returns>The result handle of the asynchrounous request.</returns>
        public IAsyncResult BeginLoad(AsyncCallback callback)
        {
            LoadAsyncResult result = new LoadAsyncResult();
            ThreadPool.QueueUserWorkItem((WaitCallback)delegate(object o)
            {
                EnsureLoaded();
                result.Completed();

                if (callback != null)
                    callback(result);
            });

            return result;
        }

        /// <summary>
        /// Ends an asynchronous load of the <see cref="Value"/> of the lazy load.
        /// </summary>
        /// <param name="result">The result.</param>
        public void EndLoad(IAsyncResult result)
        {
            if (result == null)
                throw new ArgumentNullException("result");

            if (!(result is LoadAsyncResult))
                throw new ArgumentException("The IAsyncResult specified is not an EndLoad result.");

            result.AsyncWaitHandle.WaitOne();
        }

        private class LoadAsyncResult : IAsyncResult
        {
            public LoadAsyncResult()
            {
            }

            public void Completed()
            {
                _waitHandle.Set();
            }

            private object _asyncState;
            private ManualResetEvent _waitHandle = new ManualResetEvent(false);
            private bool _completed;

            #region IAsyncResult Members

            public object AsyncState
            {
                get
                {
                    return _asyncState;
                }
            }

            public WaitHandle AsyncWaitHandle
            {
                get
                {
                    return _waitHandle;
                }
            }

            public bool CompletedSynchronously
            {
                get
                {
                    return _completed;
                }
            }

            public bool IsCompleted
            {
                get
                {
                    return _completed;
                }
            }

            #endregion
        }
        #endregion

        /// <summary>
        /// When overridden in a derrived class, loads the <see cref="Value"/> from the datastore
        /// for the given <see cref="Id"/>.
        /// </summary>
        /// <returns>The loaded value.</returns>
        protected abstract TValue LoadValue();
    }
}
